/*******************************************************************************
 * Copyright 2011 Alexandre Zglav and Morphiastic
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.morphiastic.analysis.analyzers;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;

import com.morphiastic.analysis.AnalysisComponentDefinition;
import com.morphiastic.utils.EqualsUtil;

/**
 * 
 * 
 * An analyzer of type custom that allows to combine a Tokenizer with zero or
 * more Token Filters, and zero or more Char Filters. The custom analyzer
 * accepts a logical/registered name of the tokenizer to use, and a list of
 * logical/registered names of token filters.
 * 
 * The following are settings that can be set for a custom analyzer type:
 * 
 * Setting Description tokenizer The logical / registered name of the tokenizer
 * to use. filter An optional list of logical / registered name of token
 * filters. char_filter An optional list of logical / registered name of char
 * filters.
 * 
 * 
 * IMPORTANT NOTE CONCERNING ALIAS NAMES:
 * 
 * During startup time and Analysis component creation, the tokenizer,
 * filters and charFilters names WILL BE REPLACED with the REAL names as they
 * are known by ElasticSearch server if the given name is an ALIAS. 
 * This is done so because ES doesn't know about tokenizer and filter aliasing
 * so Morphiastic has to manage its own aliasing system to avoid  uselessly 
 * duplicating Tokenizers and Filters definitions on the server.
 * 
 * For example if an equivalent tokenizer has been defined twice with different
 * names and we needed to create an alias for it, and this custom analyzer instance
 * references the alias, the tokenizer name in this instance of CustomAnalyzer
 * will be replaced with the tokenizer name as it has been saved on ES erver.
 * 
 * 
 * @author Alexandre Zglav
 * 
 */
public class CustomAnalyzer extends ElasticAnalyzer{

	public static final String PARAMS_SEPARATOR = ",";
	public static final String TOKENIZER_PARAM_KEY = "tokenizer";
	public static final String FILTERS_PARAM_KEY = "filter";
	public static final String CHAR_FILTERS_PARAM_KEY = "char_filter";
	
	private String tokenizer;
	private LinkedList<String> filters;
	private LinkedList<String> charFilters;
	
	
	
	public CustomAnalyzer(String defName) {
		this(null, defName);
	}
	
	
	public CustomAnalyzer(Map<String,String> params, String defName) {
		super(ElasticAnalyzerType.custom, defName);
		
		
		if(params!=null){
			
			String tokenizerStr = params.get(TOKENIZER_PARAM_KEY);
			if(tokenizerStr!=null && !tokenizerStr.isEmpty()){
				this.tokenizer = tokenizerStr;
			}else{
				this.tokenizer = "standard";
			}
			
			
			String filtersStr = params.get(FILTERS_PARAM_KEY);
			if(filtersStr!=null && !filtersStr.isEmpty()){
				this.filters=new LinkedList<String>();
				String[] filterz = filtersStr.split(PARAMS_SEPARATOR);
				for (String filter : filterz) {
					this.filters.add(filter.trim());
				}
			}
			
			String charFiltersStr = params.get(CHAR_FILTERS_PARAM_KEY);
			if(charFiltersStr!=null && !charFiltersStr.isEmpty()){
				this.charFilters=new LinkedList<String>();
				String[] filterz = charFiltersStr.split(PARAMS_SEPARATOR);
				for (String filter : filterz) {
					this.charFilters.add(filter.trim());
				}
			}
		}
	}
	
	

	@Override
	public boolean hasCustomParams() {
		 if(this.tokenizer!= null 
		    || (this.filters!=null && !this.filters.isEmpty()) 
			|| (this.charFilters!=null && !this.charFilters.isEmpty()) )return true;
		 return false;
	}
	
	
	
	@Override
	public XContentBuilder addToBuilder(XContentBuilder settingsBuilder) throws IOException {
//		List<String> filterList = null;
//		if(this.filters!= null && !this.filters.isEmpty()) filterList = new ArrayList<String>(this.filters);
//		List<String> charFilterList = null;
//		if(this.charFilters!= null && !this.charFilters.isEmpty()) charFilterList = new ArrayList<String>(this.charFilters);
		return addAnalyzerDefinition(settingsBuilder, this.tokenizer, this.filters, this.charFilters);
	}
	
	/**
	 * Insert this custom analyzer's definition in the provided 
	 * builder with tokenizer, filters and charfilters names provided.
	 * 
	 * NOTE the names of the tokenizer and filters MUST BE THE 
	 * ONES THAT ARE AVAILABLE IN ES server
	 * 
	 * NOTE this is a small temporary hack used to overcome the fact 
	 * that ES doesn't allow for Tokenizer and Filter aliasing.
	 * 
	 * So what we do in ElasticStore is figure out the name that was given to 
	 * the equivalent tokenizer or filter(s)and pass it here instead of calling  
	 * the other addAnalyzerDefinition method
	 * 
	 * @param settingsBuilder
	 * @param tokenizerName the name of the tokenizer as it appears in ES server side
	 * @param filterNames the names of the filters as they appear in ES server side
	 * @param charFilterNames the names of the charFilters as the appear in ES server side
	 * @return 
	 * @throws IOException 
	 */
	public XContentBuilder addAnalyzerDefinition(XContentBuilder settingsBuilder, String tokenizerName, 
			LinkedList<String> filterNames, LinkedList<String> charFilterNames) throws IOException{
		
		
		settingsBuilder.startObject(getDefName())
		.field("type", getElasticAnalyzerType().toString());
		
		settingsBuilder.field(TOKENIZER_PARAM_KEY, tokenizerName);
		
		if(filterNames!=null && !filterNames.isEmpty()){
			settingsBuilder.field(FILTERS_PARAM_KEY, filterNames);
		}
		if(charFilterNames!=null && !charFilterNames.isEmpty()){
			List<String> charFiltersList = new ArrayList<String>(charFilterNames);
			settingsBuilder.field(CHAR_FILTERS_PARAM_KEY, charFiltersList);
		}
		return settingsBuilder;
	}
	
	@Override
	public void setParamsFromSettings(Settings analyzerSettings) {
		this.tokenizer = analyzerSettings.get(TOKENIZER_PARAM_KEY);
		Settings filtersSettings = analyzerSettings.getByPrefix(FILTERS_PARAM_KEY+".");
		if (filtersSettings != null && filtersSettings.getAsMap().size()>0)this.filters = new LinkedList<String>();
		for (Entry<String, String> filterSetting : filtersSettings.getAsMap().entrySet()) {
			this.filters.add(filterSetting.getValue());
		}
		Settings charFiltersSettings = analyzerSettings.getByPrefix(CHAR_FILTERS_PARAM_KEY+".");
		if (charFiltersSettings != null && charFiltersSettings.getAsMap().size()>0)this.charFilters = new LinkedList<String>();
		for (Entry<String, String> filterSetting : charFiltersSettings.getAsMap().entrySet()) {
			this.charFilters.add(filterSetting.getValue());
		}
	}
	
	@Override
	public boolean areEquivalentDefinitions(AnalysisComponentDefinition otherAnalyzer){
		
		
		if(otherAnalyzer==null)return false;
		CustomAnalyzer oa;
		try {
			oa = (CustomAnalyzer)otherAnalyzer;
		} catch (ClassCastException e) {
			return false;
		}
		
		return 
		EqualsUtil.areEqual(this.tokenizer, oa.tokenizer)
		&&
		EqualsUtil.areEqual(this.filters, oa.filters)
		&&
		EqualsUtil.areEqual(this.charFilters, oa.charFilters);
	}

	
	
 
	

	/**
	 * @return the tokenizer
	 */
	public String getTokenizer() {
		return tokenizer;
	}


	/**
	 * @param tokenizer the tokenizer to set
	 */
	public void setTokenizer(String tokenizer) {
		this.tokenizer = tokenizer;
	}


	/**
	 * @return the filters
	 */
	public LinkedList<String> getFilters() {
		return filters;
	}


	/**
	 * @param filters the filters to set
	 */
	public void setFilters(LinkedList<String> filters) {
		this.filters = filters;
	}


	/**
	 * @return the charFilters
	 */
	public LinkedList<String> getCharFilters() {
		return charFilters;
	}


	/**
	 * @param charFilters the charFilters to set
	 */
	public void setCharFilters(LinkedList<String> charFilters) {
		this.charFilters = charFilters;
	}




	 

	 
}
